

SFINAE(替换失败不为过)原则在第8.4节中介绍，其对模板参数类型的推导有很大的影响，可以避免不相关的函数模板在重载解析期间引起的错误。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}SFINAE也适用于部分类模板特化的替换。参见第16.4节
\end{tcolorbox}

例如，考虑一对函数模板，用于提取容器或数组的起始迭代器:

\begin{lstlisting}[style=styleCXX]
template<typename T, unsigned N>
T* begin(T (&array)[N])
{
	return array;
}

template<typename Container>
typename Container::iterator begin(Container& c)
{
	return c.begin();
}

int main()
{
	std::vector<int> v;
	int a[10];
	
	::begin(v); // OK: only container begin() matches, because the first deduction fails
	::begin(a); // OK: only array begin() matches, because the second substitution fails
}
\end{lstlisting}

对begin()的第一次调用，其中参数是std::vector<int>，尝试对begin()函数模板进行模板参数推导:

\begin{itemize}
\item 
数组begin()的模板参数推导失败，因为std::vector不是数组，所以忽略。

\item 
容器begin()的模板实参推导成功，将Container推导为std::vector<int>，从而实例化和调用函数模板。
\end{itemize}

对begin()的第二次调用，参数是一个数组，也会部分失败:

\begin{itemize}
\item 
数组begin()推导成功，T推导为int, N推导为10。

\item 
对容器begin()的推导决定了将Container替换为int[10]。虽然通常来说这种替换没有问题，因为数组类型没有名为iterator的迭代器类型，所以产生的返回类型Container::iterator无效。在其他上下文中，访问不存在的迭代器类型将导致编译错误。SFINAE在替换模板参数时，将这些错误转化为推导失败，不再考虑函数模板。因此，忽略第二个begin()候选，并调用第一个begin()模板的特化。
\end{itemize}

\subsubsubsection{15.7.1\hspace{0.2cm}即时上下文}

SFINAE可以避免形成无效类型或表达式的尝试，包括在函数模板替换的即时上下文中发生的由歧义或违反访问控制引起的错误。通过定义函数模板替换的即时上下文，可以更容易地定义不在该上下文中的内容。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}即时上下文包括很多东西，各种查找、别名模板替换、重载解析等。但这个术语有不恰当，因为包含的一些活动与所替换的函数模板没有关系。
\end{tcolorbox}

函数模板替换过程中，为了推导而发生的实例化，都不在函数模板替换的即时上下文中：

\begin{itemize}
\item 
类模板的定义(即“主体”和基类列表)

\item 
函数模板的定义(函数模板本身，对于构造函数，则是构造函数初始化式)

\item 
变量模板的初始化式

\item 
默认参数

\item 
默认成员初始化式

\item 
异常规范
\end{itemize}

不是函数模板替换的即时上下文的一部分。由替换过程触发的特殊成员函数的隐式定义，也不是替换的即时上下文的一部分。其他一切都是上下文的一部分。

因此，若替换函数模板声明的模板参数需要实例化类模板的主体，该类的一个成员正在引用，那么实例化期间的错误并不在函数模板替换的即时上下文中，因此是一个真正的错误(即使另一个函数模板匹配没有错误)。例如:

\begin{lstlisting}[style=styleCXX]
template<typename T>
class Array {
	public:
	using iterator = T*;
};

template<typename T>
void f(Array<T>::iterator first, Array<T>::iterator last);

template<typename T>
void f(T*, T*);

int main()
{
	f<int&>(0, 0); // ERROR: substituting int& for T in the first function template
} // instantiates Array<int&>, which then fails
\end{lstlisting}

此示例与上一个示例的主要区别在于错误位置。前面的例子中，错误发生在typename Container::iterator类型时，该类型位于函数模板begin()替换的即时上下文中。本例中，失败发生在Array<int\&>的实例化中，尽管由函数模板的上下文触发，但实际上发生在类模板Array的上下文中。因此，SFINAE原理在这里不适用，编译器将产生错误。

下面是C++14的例子——依赖于推导的返回类型(参见15.10.1节)——在函数模板定义的实例化过程中会产生一个错误:

\begin{lstlisting}[style=styleCXX]
template<typename T> auto f(T p) {
	return p->m;
}

int f(...);

template<typename T> auto g(T p) -> decltype(f(p));

int main()
{
	g(42);
}
\end{lstlisting}

调用g(42)将T推导为int。在g()的声明中进行替换需要确定f(p)的类型(p现在已知是int类型)，因此需要确定f()的返回类型。f()有两个候选。非模板候选匹配，但不是很好，因为它匹配一个省略号参数。不幸的是，模板候选有一个推导的返回类型，因此必须实例化定义来确定返回类型。这个实例化失败的原因是p\texttt{->}m在p是int时无效，而且由于失败在替换的即时上下文之外(因为在函数定义的后续实例化中)，失败产生了一个错误。因此，若可以显式指定推导返回类型，这里建议不使用。

SFINAE最初的目的是为了消除由于函数模板重载意外匹配而导致的错误，比如:容器begin()的例子。但检测无效表达式或类型的能力可以实现更卓越的编译技术，允许判断特定语法是否有效。这些技术将在第19.4节中讨论。

请参阅第19.4.4节，以获得使类型SFINAE友好的例子，以避免由于即时上下文问题造成的问题。



